//////// // // // // // /// // // // // //// // // //////// // // // // // // //// // // // /// // // // // /////// Pascal NewsLetter Issue #5 December, 1990 Editor: Pete Davis The Programmer's Forum BBS is the home of PNL. It can be reached in Washington, DC at (202)966-3647. Information is available through the following locations: FidoNet: Pete Davis@1:109/138 GEnie: P.DAVIS5 BitNet: HJ647C@GWUVM & UE356C@GWUVM InterNet: HJ647C@GWUVM.GWU.EDU or UE356C@GWUVM.GWU.EDU Uucp: Pete.Davis@f138.n109.z1.fidonet.org 2 Table of Contents Introduction ............................... 3 (P.D.) The Lazy Programmer's Guide to Graphics in Pascal ...................... 4 (R.M.) Introduction to Multiple Concept Pascal Programming ......................... 15 (P.R.) For Beginners .............................. 22 (B.G.) Recursion is Easy .......................... 32 (C.B.) Conclusion ................................. 38 (P.D.) Distribution List .......................... 40 P.D. -> Pete Davis (Editor-in-Chief, Writer) R.M. -> Richard A. Morris (Editor-Over-the-Pond, Writer) B.G. -> Bob Gowans (Staff writer) P.R. -> Paul Robinson (Contributing writer) C.B. -> Chris Burke (Contributing writer) 3 Introduction Well, finally getting to another issue of the Pascal NewsLetter. We're making a few changes in the organization of PNL. First of all, I've promoted myself to Editor-in- Chief. That's really what I've been all along, but it was to make room for some new titles. We've got a new staffer, Richard A. Morris, with the distinguished title of 'Editor-Over-the-Pond'. Richard will be a collection point in Australia for PNL contributions and will be editing and sending them on to me for inclusion in the NewsLetter. For a profile of Richard, read the end of his article. If you live in Australia, send your Contributions to Richard A. Morris. His Fido-Net Address is 3:640/378. His system's phone number is 61-7-878-1194. Richard is also writing "The Lazy Programmer's Guide to Graphics in Pascal", a series of articles about, you guessed it, graphics programming in Pascal. It's very thorough coverage of graphics, so many of you will surely find it interesting. Many of you probably already know that I had been planning on putting the newsletter out as 'Subscription- only'. Well, I ran into some problems un-related to the newsletter which have forced me to postpone, at least until summer, this course. Also, I would like to thank everyone who has been contributing. It seems that the number of submissions is increasing slightly, which is a terrific bonus. If you can keep them coming in, the newsletter just might find a way to come out a bit more often. In this issue, Paul Robinson will be talking a bit about writing large programs in Pascal and will cover some programming techniques. Bob Gowans continues his popular "For Beginner's" column, and last, but not least, Chris Burke shows you how simple recursion can be. Well, that's about it for this issue. I hope you enjoy it. 4 The Lazy Programmers Guide to Graphics in Pascal by Richard A. Morris Fido 3:640/378 Brisbane, AUSTRALIA I have been running a BBS and reading FIDO echoes for some years now and invariably the first 2 things Novice programmers want to write with their new language are a better BBS, and a better Graphic Game. Well I'll leave the intricate subject of Data communications to the "Build a better Mousetrap "experts, and Game theory to the Academics, Rather in the next 6 Articles I'll discuss Graphics programming with Turbo Pascal. The Idea behind the Lazy programmers Guide, is that if you are like me, you often wondered how easy it would be to do some particular programming task, but never had the time or inclination to get around to opening the manual at that section. Well this article is both for the novice, and the experienced (Lazy Programmer) and will take you from very simplistic exercises to quite complex Graphic programming in a short time, by program examples, and prodding your imagination to experiment a bit :-). These articles are based on Turbo Pascal on the PC, and I apologize in advance to those not using that, in particular versions 4, 5, and 5.5 (And I apologize in Advance to all Americans as I intend to use the word Color a lot :-). I am sure that the topics I intend to cover will have some relevance for all Graphic programming. I don't promise that you will be able to write a better Flight Simulator (Although if you do I want a mention :-) but you will understand a bit more about how it's done, and probably gain a lot of respect for these Graphic Wizards. The articles will assume that you have some knowledge of programming concepts (Such as Binary mathematics, Trigonometry) and Turbo Pascal (there will be very little handholding :-), and will take you from simple graphics to quite Advanced programming concepts. The Articles will comprise the following Topics; Basic Graphics with Turbo Pascal GRAPH unit. Graphs, Statistics, and windows with the GRAPH unit. Simple Animation and Sprites using the GRAPH unit. User defined programming in the GRAPH Unit Writing Directly to the EGA/VGA. Animation using Direct writes to the EGA/VGA. Three Dimensional Graphics directly to the EGA/VGA. 5 ------------------------------------------------------------ The Lazy Programmers guide to Turbo Pascal Graphics - Part 1 - Basic Graphics with the GRAPH unit - ------------------------------------------------------------ A Brief History of PC Displays - Apologies to S. Hawkins :-). ------------------------------------------------------------ Graphics in the Personal computer world refer to non-text display information. The original PC released a decade ago, was equipped with a display called the Monochrome Display, and a driver called (Surprise surprise) the Monochrome Display Adapter. This Display had a resolution of 720x350 and displayed ONLY the IBM PC character set in black and white. It was not long before the popularity of computers such as the Apple II, with it's bit mapped display, caused demand for a graphic display, and the Hercules (from a PC add-on company Hercules Computer Technology) was born. The Hercules still displayed data in black and white, but it allowed you to show bitmapped graphics. Soon IBM got the hint and released the Color Graphics Adapter with 16 color text and 4 color bit mapped graphics, unfortunately the CGA design featured a lot of compromises such as display to inferior composite displays, and offered poor display resolution (320x200), but it did have Color, and it is one of the most popular displays around. Eventually due to increasing market pressure from companies like Apple, Atari, and Commodore, IBM brought out a reasonable display called the ECD, and drove it with the Enhanced Color Adapter, this gave programmers access to a resolution of 640x350 with 16 simultaneous Colors from a palette of 64. Lately VGA technology has become a standard and it increases displays up to 640x480 and 256 colors (at 320x200). These articles will concentrate on the EGA and VGA modes, but the GRAPH unit stuff will cover some aspects of general graphic device programming, and this first part only requires a CGA Screen.. BGI - The Borland Graphics Interface ------------------------------------ Due to the nature of the PC market, any programmer who wrote his code for any particular Graphic standard limited his earnings by limiting his User base to only that machine. True, most of the standards are Downward compatible, which 6 means that a CGA only application would run on a VGA, but given the choice a VGA owner would surely choose a product that displayed VGA graphics. Until Borland brought out BGI's most Languages did not support Graphics at all, relying on third party toolboxes, with Turbo Pascal and Turbo C Borland introduced the concept of a Graphic Interface whereby a programmer wrote code to call the BGI which would then do all the hard work of working out what device it was talking to and how to display that information. This is a great concept, as it provides portability of programs across graphic devices, but of late a lot of programmers have decided to bypass Borlands interface to talk instead directly with the graphic device. The main reason for this is speed, Borland Drivers use the computer BIOS in a lot of cases (Which itself wastes time deciding what device it is talking to), and are themselves quite slow, hence I shall devote a fair bit of time in later Articles on programming Directly to the Graphic device. Setting up to display Graphics ------------------------------ Firstly to Show graphics on our monitor we must tell the device to get into graphics mode, at the end of displaying what we want we must also return the system to the original display mode. Additionally as we will be using BGIs during this article, we must tell Turbo pascal where to find our BGI control files. A sample of a simple program to get into graphics mode display a line of text (More of this in this article), and go back to text mode is in the following program put it into a file called GRAPH_TE.PAS. To compile it load it into the editor, change line 7 to make BGI_Directory point to the directory you have put all the BGI files into (eg:I use c:\TP\BGI). ~ Prog 1 ~~~~~~GRAPH_TE.PAS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {$M 16384,16384,655360} Program Graph_Test; Uses CRT, GRAPH; Const BGI_Directory = 'C:\TP\BGI'; var GraphDriver : Integer; GraphMode : Integer; 7 ErrorCode : Integer; tempstr : String; begin GraphDriver := Detect; InitGraph(GraphDriver,GraphMode,BGI_Directory); ErrorCode := GraphResult; if ErrorCode <> grok then begin Writeln('Error initialising Graphics :',ErrorCode); Writeln('Error Message :',GraphErrorMsg(ErrorCode)); Halt(1); end; {Graphics stuff begins} Str(GraphMode,TempStr); OutText('We are in Graphics mode number :'+TempStr); Str(GraphDriver,TempStr); OutText('This is Device number :'+TempStr); {End of Graphics stuff} delay(1000); CloseGraph; end. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ O.K. let's dissect this test program, firstly in line 4 we USE the GRAPH unit, this will give us access to all of the graphics routines, and constants. We declare the directory that all the BGI files will be found into a constant, and declare 4 variables GraphDriver to store the type of graphics device we have, GraphMode to store the graphics mode we will use, ErrorCode to store any error numbers, and TempStr to temporarily store integers as strings. Then we set GraphDriver to the GRAPH constant DETECT. DETECT is defined as 0 and will tell Turbo Pascal to automatically detect the Graphic device connected to the PC, and select for us the maximum available Graphics mode. The next line calls the GRAPH function INITGRAPH, we tell this function what device to talk to (or AUTO detect in our case), which mode we want (In this case the Auto detect will select one for us), and the location of the BGI files. We can actually pre-select the type of device by specifying the GraphDriver and GraphMode before running InitGraph, as in the following inset (Replace Grafdriver := detect). ~~~~~~~~~~~~~~~~~~~~~~ GraphDriver := CGA; GraphMode := cgac0; ~~~~~~~~~~~~~~~~~~~~~~ 8 This will set up the Graphic device for working with a CGA compatible device, running in mode CGAc0, or the first CGA color mode. InitGraph allocates some memory on the heap to work with, so you must have some heap memory available (Hence the $M directive in line 1), such as a general 4K buffer for graphics, and space to put the BGI file. The GRAPH function GraphResult will have an errorcode after most GRAPH routines and will be zero if the routine proceeded without error, or will contain an error number if an error occurred, the function GraphErrorMsg can give you a textual error message if you send it the error number as we have in the test program. If an error occurs the program describes the error and aborts. Assuming successful initialization of the Graphic system the next step is to perform the Graphics routines, which in this case is to write out a message describing the Device that autodetect found and the Mode. We have to use the str routine to convert integers into strings, as the Graph text writing expects a string as a parameter, and can't use the abilities of write and writeln. Finally we close the graphics drivers with CLOSEGRAPH. Close Graph releases all memory assigned by InitGraph and restores the original text mode. Now when you run the program it will quickly display the graphics data, and jump back to text mode, so I've inserted a delay in before the call to close graph. You may notice a delay before loading graphics while the system loads the BGI file, in some systems this may be unacceptable or you may wish to distribute just on EXE file and not include a whole lot of BGIs, so Borland have given you a demo of how to do this in the example file BGILINK.PAS. If you have problems with this sample file you should check that you have a graphics capable device, and that the BGI files are indeed where you specified them. Displaying Pixels ----------------- O.K. so far you've managed to Display some funny shaped text on the screen, Big Deal you say. Well from here on in this part we will use the program Graph_Test, to set up the graphics device, and close it after use, and in place of the above Graphics stuff (In this case the OUTTEXTs) we put in specialized code. The next step is to display a point on the screen, we call one of these a PiXel or Picture Element. As you know different Display technologies have 9 different Screen resolutions (Pixels on the Screen) for example you fit 320 pixels across and 200 down in CGA mode 5. In the BGI routines, you can put a pixel in a location that is not on the screen. This may seem strange, but when you realize that under EGA you can put a pixel in the last column at column 639 (the first column is always 0 so 0-639 = 640 pixels), which would be way of the screen in CGA. For the purposes of this Article we'll do most of our work in the range of 0 - 99 columns and 1 - 99 rows, which will be displayable on all monitors. The routine to display a pixel on the screen is (Surprise) PutPixel(x,y : Integer; Color: word) X and Y are not surprisingly Column and Row coordinates, and Color is the color of the pixel. O.K. so lets do some Graphics. Insert the following declarations at the beginning of Graph_Test, and the routine in the Graphics stuff section. ~ Declarations ~~~~~~~~~~~~~~~~~~~~~~~~ VAR Counter : Integer; Color : Word; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Color := 1; For Counter := 0 to 99 do begin PutPixel(0 ,Counter,color); PutPixel(99 ,Counter,color); PutPixel(Counter,0 ,color); PutPixel(Counter,99 ,color); end; PutPixel(50 ,50 ,Color); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This routine will Firstly set the variable color to 1 (The first available color, in which case all devices will show a color distinct from the background color), which in most modes is Dark blue, in the 320x200 cga modes this will be Green, or Cyan. The system then counts from 0 to 99 and puts a box on the screen, by plotting a point in the left, right, top and Bottom edge. To see how it does this dot by dot put the line DELAY(100) before the end in the FOR loop, you will see each dot being drawn. Then it will put a dot almost in the middle of the box, depending on the resolution of your display mode, the box could be anything from a perfect square to an elongated rectangle, GRAPH has a procedure called GetAspectRatio which will tell you the ratio you may use to draw a perfect square (We'll talk about this later on), and various functions and procedures to ask the Graphics device how wide and tall (in pixels) it is, we will discuss these in the next article on Graphs. 10 Play with the colors for your default (Auto detected) mode, then try playing with the Graphdevice and Graph mode, to get the hang of your Graphics device, as you will need to know later on about the capabilities of your system. You can also move the x,y position of the single pixel, or even draw some simple shapes. An example of a little function to draw a shape would be to stick this line in the FOR loop. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PutPixel(Counter,50+trunc(sin((counter/100)*2*pi)*20),color) ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It plots a point for each x position of counter, based on a sin function (A Turbo function that gives a value on a sin curve (Wave !?!) between -1 and 1 for every position between 0 and 2*Pi radians). TRUNC converts the Sin value from a Real into an integer for PutPixels Y value, sin((Counter/100)*2*pi) gives a real between -1 and 1 as (Counter/100) goes from 0 to almost 1, and we multiply this by 20 to make the wave oscillate between 50-20 and 50+20 to put the wave in the middle of the box. Lines, Arcs and other Objects ----------------------------- Drawing a line is a bit slow plotting a pixel at a time, so Turbos GRAPH unit includes a LINE function so firstly we'll replace our FOR Loop box Drawing routines with; ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetLineStyle(SolidLn,0,NormWidth); SetColor(1); Line(0,0,0,99); Line(0,99,99,99); Line(99,99,99,0); Line(99,0,0,0); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You will also notice that LINE doesn't include a color parameter, GRAPH has a routine called SETCOLOR (Which we set before we draw the lines) which defines a color to be used by all line and object drawing routines. Graph has another surprise with a routine called SETLINESTYLE which allows us to select the style, pattern, and thickness of the lines 11 we'll draw, again I've included that one above for a SolidLn, but you might like to try styles of DottedLn, CentreLn, or DashedLn and a Thickness of Thickwidth. Pattern is a special value we'll discuss in the next topic. There is an even Better method to display a Box called RECTANGLE, which combined the 4 lines commands, try this. ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetLineStyle(SolidLn,0,NormWidth); SetColor(1); Rectangle(0,0,99,99); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now try this, it uses three more line based object drawing routines; ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetLineStyle(SolidLn,0,NormWidth); SetColor(1); Rectangle(0,0,99,99); Circle(50,50,40); Arc(50,60,225,315,20); Ellipse(40,40,0,360,5,10); Ellipse(60,40,0,360,5,10); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ What these routines do is Draw a Circle with the centre at 50,50 with a radius of 40 pixels (This routine uses GetAspectRatio to makes as round a circle as possible), Arc draws a part of a circle with a centre at 50,60 (10 pixels below the centre of the circle) going from 225 degrees (Unlike the Trigonometric functions the Graphics routines represent angles in Degrees, starting from 0 horizontally to the right) to 315 degrees with a radius of 20 pixels, the two Ellipses draw two identical squashed circles with centres 10 pixels either side of the middle point, and 10 pixels above it, each ellipse goes from angle 0 around a full circle to 360, and with a x radius of 5 and a Y radius of 10. If this explanation seems a bit obtuse, run the demo first and you'll see what I mean . The Last Line Based Object drawing routine is the PolyDraw routine which will draw any polygon, although it needs a bit of setting up. What you have to do is define a variable as an array of pointType which is a GRAPH global type, a record of (x,y) coordinates. Then you fill the array elements with x,y coordinate data (I often use Graph paper to work out how I'm going to do a shape), and simply call the DrawPoly routine with the number of points, and the Shapearray, try this one which draws two triangles on our face shape; ~ Delcaration ~~~~~~~~~~~~~~~~~~~~~~~~~ 12 Triangle : Array[1..4] of PointType; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Triangle[1].x := 50; Triangle[1].y := 85; Triangle[2].x := 20; Triangle[2].y := 75; Triangle[3].x := 20; Triangle[3].y := 95; Triangle[4].x := 50; Triangle[4].y := 85; DrawPoly(4, Triangle); Triangle[2].x := 80; Triangle[3].x := 80; DrawPoly(4, Triangle); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Patterns and fills ------------------ O.K. from the previous program, add the following lines after that last DrawPoly routine; ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetFillStyle(solidFill,Color); FloodFill(50,5,Color); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A fill is a bit like a color-in that kids do, it will fill an area with a color. Now this one takes a bit of explaining, firstly SetFillStyle sets the type of fill any solid routine will use, and the color that it will color in with, in this case it will be a SolidFill, you can fill with diagonal lines (SlashFill), or hatchs (HatchFill), dots (CloseDotFill) or others, play around with it. The second line calls the fill, by specifying the x and y position of any point within the area to fill, and the color that will border the area. If there are any breaks in the border then the flood fill will leak through, try putting this routine to remove one of the bowties edges in before the FloodFill Routine, you'll notice the floodfill will bleed into the second triangle; ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetColor(0); Line(Triangle[2].x,Triangle[2].y,Triangle[3].x,Triangle[3].y ); SetColor(1); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can draw the polygon and fill at the same time, 13 with a GRAPH routine called FillPoly, remove the previous routine to delete the edge of the bowtie, remove the floodfill, and Change the two DRAWPOLYs into FILLPOLLY and put ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetFillStyle(WideDotFill,Color); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ anywhere before both FillPolys and the routines will perform a poly draw AND a fill at the same time. You can use FillEllipse in the same way to draw and fill an ellipse, or Bar to draw and fill a rectangle. Text and Fonts -------------- To draw text there are several commands the first SetTextStyle loads up BGI files for the Font type (Much like initgraph sets up the Device BGI files), sets the direction of the text (Try this next procedure with VertDir instead of HorizDir, and set the text to start at 10,5), and the size of the characters. The second Command OutTextXY(X,Y : Integer;Text : String) sends text of the defined Style to the output port at the requested X,Y location. ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetTextStyle(DefaultFont,HorizDir,1); OutTextXY(20,5,'Mr Happy'); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Try different commands such as different fonts (TriplexFont, GothicFont), different sizes (Each fonts text will be a different size), and different orientations. One major problem with Ad hoc text placement in Graphics modes is working out where to put the next line, as you probably won't know where the current text finishes and the next should begin. Turbo provides two procedures TextHeight and TextWidth for just this problem. Simply said they return the size in pixels take up by a string. Let's go straight to an example, and I'll show you what I mean. Remove all the old graphic stuff leaving only the Box routine, and after drawing the box, add the following; ~ Routine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SetTextStyle(DefaultFont,HorizDir,1); Charheight := TestHeight('M'); OutTextXY(20,5,'Simple example'); OutTextXY(20,5+textheight,'of text spaced'); OutTextXY(20,5+textheight+textheight,'at even heights'); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 As you see you will get each new line starting after the previous line has finished, regardless of font, character, or graphics device. Summary ------- Well I hope I haven't offended too many sensibilities with the simplistic nature of the exercises, we have gone over some very basic Graphic concepts here, that are fundamental to further understanding graphic manipulation. Next PNL I will be discussing Business, mathematic and Scientific Graphics such as Bar, Pie, Scatter, Hi_Lo graphs, the graphing of mathematic functions in 2 and 3 dimensions, and Fractal representations such as Sierpenski, Mandlebrot, and Benoit sets. As well as topics, like windows, Auto Scaling graph axes, and further discussion of aspects of text drawing. Author ------ Richard Morris is the Owner/operator of KHIRON Software, in Brisbane AUSTRALIA, a company that specializes in Graphic Databases in Turbo Pascal for Health care professionals, as well as providing contract staff for as diverse projects as Robotic process control software, to various Industry support software. Current Supported Packages DENTAPAC tm - Dental practice management tools, PHYSIOPAC tm - Phyiotherapy management tools, and ORTHOPAC - Orthodontic practice management tools. References ----------- The Turbo Pascal Reference Guide (C)89 Borland Int. Advanced Programmer's Guide to the EGA/VGA George Sutty/Steve Blair Advanced MsDos Ray Duncan INTER490 Ralf Brown 15 (C) Copyright 1990, Tansin A. Darcos & Co. Commercial Reproduction rights are reserved. Introduction to Multiple-Concept Pascal Programs Paul Robinson Most large or commercial programs tend to do a number of things. I was trying to do a program that would count all the words in a text file - which might be almost any line format, and produce an alphabetical list of words. This meant that I had to do several things: o Read a file that might have Carriage Return ("CR") followed by Line Feed ("LF") for an end of line, LF then CR, LF only, or CR only, or might be from one of those weird programs that just generates "stream ASCII" in which it takes words until the line is full then at the next space issues a "soft" carriage return. The only requirement was that the file be non-coded, i.e. that characters appear as normal character values (Wordstar, for example, puts a high-bit code on some words to use as paragraph or line breaks). o When reading the words, store them in memory. The program might read a small file or it might read a 1/2 million word book, so the number of words would be indeterminant, and the word sizes would all be different. o List each word and the number of times used, in alphabetical order. The first qualification precluded the use of Pascal's standard READLN/EOLN construct to check for end of line. This meant I'd have to read the file in on a character by character basis. The second provision was because, with as much memory as is on a PC, there is really no need - unless you get a huge file - to use disk space to store word counts (also because of something I was trying to do that I wanted to use a in-memory word list). The third provision was what I had wanted: an alphabetical word list. (If I had been more concerned with the number of uses, I would have had the words listed in order by number of usages rather than alphabetically). These provisions made the choice of using a linked list quite obvious. The questions that came up included what type of linked list to 16 use and how to implement it. That will be discussed below. Understanding Pointer Variables Paul Robinson One of the things I needed to do one day was read one or more disk files and create an alphabetized list of the words in each file, for something I was working on. I wanted to store the list of words used in the file in memory because I did not want to spend a lot of time reading and writing the disk in addition to all the disk reads being used to read the original file(s). Since the number of words to be stored would vary, storing them in arrays might either not provide enough memory space or waste a lot of memory. Pascal (and other languages) have a capability in which a variable can point to a block of storage that only exists after the program explicitly requests to be issued that storage. Arrays and variables are created (for the main program) when it is executed, and exist until the program ends. This special storage is allocated dynamically if, as and when needed, by the programmer specifically asking to be given a block of this memory. The programmer can also give back this memory when finished using it, and perhaps even re-use it later on. To access this dynamic memory, the programmer creates a class of variable called a pointer. A pointer is a variable that describes a memory location indirectly. Let me explain that a little closer. An ordinary variable exists at a specific location in memory, and contains a value such as a number, or a character (or two). This is an ordinary, direct access to a value. A pointer, however, goes one step further, in that it contains not a value, but the address of a value. For example, let me examine a record: TYPE maxstring = STRING[255]; tokp = ^tok ; tok = RECORD next : tokp ; count : LONGINT ; token : maxstring; END ; 17 The first item indicates that the identifier 'maxstring' is a string of the maximum size. Next, I indicate that the identifier 'tokp' is a pointer to (that's what the ^ indicates: "Pointer to") the identifier 'tok'. But tok is not defined until after tokp. This is the one exception to the rule in Pascal that everything must be defined before use; pointers may refer to identifiers which are not defined when the pointer is. Tok is a record beginning with a 'next' field, which since we know tokp is a pointer, that 'next' is a pointer to another record of the same type. The 'count' variable is a long integer, and 'token' is a string of maximum length. Why is there yet another pointer when one is already defined? The 'next' field is what is called a 'traverse' pointer. In order to find records in a linked list, you have to be able to follow ('traverse') the list in whatever order it is maintained. The way to do this is to change the pointer that looks at a record from the current record to the next one in the list. This would be done by assigning the 'next' value to the pointer which is referencing this record. Some pointer records will also carry a 'back' or 'prev' pointer to go backward in the list. Note that all this has done is create a set of names so that we can use this record - or the tokp pointer- in the program, but we cannot yet use them because we did not define variables that do, like this: VAR name:maxstring; T,t1: tokp; We still can't access the memory, because the pointers do not really contain the data, they contain only the address of the data. We have to allocate the space for that data in order to be able to access them. There are two ways that a programmer can be given access to dynamic memory. Both require the programmer pass a pointer to a procedure that allocates memory to that pointer. The first method is the NEW procedure, which is called in the form NEW(T); however, in our instance, allocating 255 bytes for each word probably wastes memory, especially since each word probably is no larger than 20 characters. There are two ways around this: pick a maximum and assume no word will be larger than that, or size the records to fit, which is option 2. 18 The second method involves knowing what the program does; since I will know that 'name' will contain the string to be stored in the 'token' field of the tok record. So, I could allocate a specific sized block of memory with a special procedure available in Turbo Pascal called GETMEM, and it works like this: GETMEM(T,LENGTH(NAME)+10); where the first argument is the pointer to use, and the second argument is the amount of memory in bytes to allocate to the pointer. The LENGTH(NAME) value gives us the actual size of the string, the +10 is because the pointer takes 4 bytes, the longint takes 4 bytes, the length of the string adds one byte, and I want the next allocation to come on a word boundary. In either the NEW or GETMEM options, the variable is now ready for use. We can access it by referencing the pointer followed by a fieldname: T^.Count := 1; or T^.Token := Name; T^.Token := Name; T^.Count := 1; Also, it is usually best to ensure that any unused or traverse pointers are cleared, so we should also say: T^.Next := NIL; (NIL is a special value which indicates a pointer is not in use). If we later add another record, we have to keep the old one, so we might do something like this: T1 := T; WHILE T1^.Next <> NIL DO T1 := T1^.NEXT; GETMEM(T1^.Next,LENGTH(Name)+10); T1 := T1^.Next; (I am trying to simplify this example. Things like alphabetizing the entries, checking for duplicates, etc., has been omitted.) When finished with the memory pointed to by a pointer, you release it back to the system. On some systems they use MARK and RELEASE, in which you create a mark pointer, and store the current memory list in the marked pointer with MARK(markpointer); 19 then when you don't need the memory any more, you get rid of it with RELEASE(markpointer); This cancels all usage of the NEW procedure done after the MARK procedure call. Any pointers allocated after that MARK are now invalid. Some systems do a better job at allocating memory, in which you use the NEW procedure (or on Turbo Pascal, you can also use GETMEM), then when you don't need a particular piece of memory, you can use DISPOSE (if you called NEW) or FREEMEM (if you called GETMEM). You would use them like this: NEW(T1); ... DISPOSE(T1); (* OR *) GETMEM(T1,LENGTH(NAME)+10); ... FREEMEM(T1,LENGTH(NAME)+10); Note: if a program is exiting, it does not have to release the memory it allocated; the system will do so for it. Pointers do not have to point to records. In some systems, pointers are used as a means to simulate the PEEK/POKE function from BASIC, as in the following example: PP = ^CHAR; VAR P: PP; I: array[1..2] of integer absolute P; Colr,Mono,Both:boolean; BEGIN I[2] := $B800; I[1] := 0; Colr := FALSE; Mono := FALSE; P^ := 'A'; IF P^ = 'A' THEN Colr := TRUE; I[2] := $B000; 20 I[1] := 0; P^ := 'A'; IF P^ = 'A' THEN Mono := TRUE; IF Colr and Mono THEN Both := True; ... This little program segment checks to see if a color adapter is installed, then it checks to see if a monochrome adapter is installed, by checking the physical addresses to see if there is memory in their spaces. This piece of code is here to show that a pointer can point to something other than a record. Turbo Pascal has the MemW[] and Mem[] arrays, but some compilers do not, and this is one way it may be simulated. Understanding A Linked List Paul Robinson Going back to my original idea, I wanted to create an alphabetized word list. I wanted merely to do a single linked list where each item points to the next one in memory. If I checked the words first, I could store the table automatically in alphabetical order and thus generating the table would be trivial. Almost all the books and articles I see deal only with double-linked lists, where there is a pointer to a previous record, and a pointer to a record that is next in line. This would be overcomplicated for something as simple as just generating a list of words in alphabetical order. I didn't want frequency counts in a binary tree, I just wanted a list of words - and it seemed trivial. I have used Pascal for over 5 years and doing this little exercise taught me something about the language and how it works. It also taught me a little about the way I was thinking about certain concepts. Every time I started on the means to store the table, I kept either losing items or not storing the new entries. The standard method for working with a linked list is to have a start pointer, and additionally to have a 'traverse' pointer, as described above. You follow down the links until you find the entry you want or until you run out of items. One thing I had misunderstood was that when you have a second pointer, the pointer is not the data, and two pointers pointing to the same place are not the same 21 thing. This means, if you have var T,T1:Tokp; name:maxstring; begin NEW(T); T1 := T; That any change to T1 will not have any effect on the system, unless you do something to one of the fields pointed to by t1. Remember, just because T and T1 point to the same place do not make them the same thing. And that is what caused me so much trouble. In the enclosed program (WORDCOUNT.PAS) is the word count program I wrote. If you notice the section where T2 is allocated then the values are all moved: IF t1^.token > tab.sym THEN BEGIN new(t2); tab.nwords := tab.nwords+1; t2^.count := t1^.count; t2^.token := t1^.token; t2^.next := t1^.next; t1^.count := 1; t1^.token := tab.sym; t1^.next := t2; END; Merely assigning values to T2 would not have worked, because T2 is local to that procedure. The only way to get the values to stick is to use the existing values and attach these. This program sample was added to fix two glaring deficiencies: the lack of an explanation in the use of single linked lists, and one mistake I was making, confusing the reference pointer with the data. My guess is that if I am doing this, others are also. 22 For Beginners By Bob Gowans Having discussed important features of programming such as design, input and output statements in Pascal, the use of relevant comments and programming style we can give ourselves a self congratulatory pat on the back, but... before you can achieve the ultimate goal, writing that elusive master program, complete with pop up menus and user friendly screens you must learn to master the art of being selectively repetitive in your programming technique. The computer is really worth it's weight in gold when it can carry out, on our behalf, long and boring tasks producing results at amazing speed. For example a payroll program, used by a large company, has to calculate net pay of each of it's employees - this is essentially a repetitive task. The same program may base it's calculations on variable tax rates, depending on gross pay. Thus, the program has to select a course of action for certain circumstances. Lets take the issue of selection and repetition and treating each as a separate concept briefly consider their use in program design. Selection ========= When we are faced with a problem we usually embark on a course of action arrived at through a mental process called decision making. Decisions are an important part of our everyday lives, we make countless decisions each day and branch out along different courses of action to achieve what we want. From many different alternatives we SELECT a particular course of action. IF I get home on time tonight THEN I will watch the game on T.V. ELSE I will read PNL might be a simplified way to express a form of decision making. In terms of program design decision making is an important concept and one way to deal with this is very similar to the human decision example given above. We would express this as follows :- if ( test of truth of a statement) then carry out a set of instructions when the test is true else carry out a set of instructions when the test is false ifend For example, what follows is a design language implementation of a simple selection statement which tests whether a person is married or not and writes out a message accordingly. 23 1.1 if married = true 1.2 then 1.3 write out 'congratulations' 1.4 else 1.5 write out 'keep trying' 1.6 ifend If the person is married he gets the message 'congratulations', if not then is presented with the 'keep trying' message. Remember the position of the words if, then, else and ifend and note the indentation of the actions to be performed. Repetition ========== Repetitive execution of program statements involves the use of loops. A conditional loop is controlled ie. started or finished, by simple or complex conditions.There are several ways in which loops can be implemented , initially we will deal with a type that tests for the stopping/starting condition at the beginning of a loop. At this stage we will not be dealing with unconditional loops, this type of loop will execute a predetermined number of statements, however it is as well that you are aware that they exist. If the loop is to be executed while a condition is true, then the loop control is written as LOOP WHILE condition. The loop will take the following form :- loop test (ie. loop while condition is true.) statement 1 statement 2 etc... loopend It is very important to observe two rules when using conditional loops. The first rule is to initialize any variables, prior to entering the loop, which will be updated within the loop. The second rule is to initialize any variables used in the loop test ( ie. the condition ) prior to entering the loop. To illustrate the importance of this lets consider the following design which repetitively reads in a real number from the keyboard and sums the total of all the numbers entered and outputs the final total. 1 initialize variables updated in the loop 2 read in first number 3 loop while number is positive 4 update total 5 read in next number 6 loopend 24 7 write out total Note that total is a variable that is consistently updated, within the loop and that number is the variable used to determine the condition of the loop ( ie. continue or stop ). This information can be tabulated as follows :- Identifier Description Type ========= =========== ===== number number input real variable total accumulated total of numbers input real variable Following the top-down methodology lets individually refine the steps given above. Steps 2,4,5 and 7 are easy :- 2.1 read in number 4.1 set total to total + number 5.1 read in number 7.1 write out 'The total sum of all the numbers is ',total. Lets not forget to include relevant input prompts. Steps 2.1 and 5.1 will require further refinement. 2.1 write out 'Enter any positive number (negative number to quit)' 2.2 read in number 5.1 write out 'Enter any positive number (negative number to end)' 5.2 read in number The two steps that read in number at 2.2 and 5.2 are required because the initial condition of the loop at step 3 depends on the value of number read in at step 2.2 - we have thus met the requirement of loop rule #2 by setting number to it's initial value. Once the loop has been entered control passes through steps 4.1,5.1,5.2 until at step 6, control passes back to step 3. The condition of the loop is tested again and it's execution depends on the data entered at step 5.2. The two input statements are necessary - the first to test the loop initially and the second to provide subsequent test of the loop condition. 25 Refining step three requires us to see that the condition is true as long as number is positive. So if number is entered as a negative the loop will terminate. This is a simple condition and can be expressed as follows :- 3.1 loop while number >= 0 If you are unsure what the condition operators mean, refer to the table under the section IMPLEMENTATION OF IF THEN ELSE which is further on in the article. The stopping data item ( ie a negative number ) is known as a SENTINEL VALUE. If the number variable has a negative value the loop condition is not true and the loop is not executed. In this case the execution of the loop is controlled by user input. To further refine step 1 we require to know the variables that will be updated in the loop. Both total and number are updated within the loop but only total is updated by assignment ( number is updated as being input ), so total requires to be initialized before being used in the loop. This would necessitate setting total to zero. The complete design is given below : 1.1 set total to 0 2.1 write out 'Enter any positive number (negative number to quit)' 2.2 read in number 3.1 loop while number >= 0 4.1 set total to total + number 5.1 write out 'Enter any positive number (negative quits)' 5.2 read in number 6 loopend 7.1 write out 'The total sum of all the numbers is ',total. We have dealt, briefly with a method of selection and a method of repetition and program language methods which allow these concepts are called CONTROL STRUCTURES. As you guessed there are other control structures but we will leave those for another article. Lets go on to implement the two control structures we have discussed in Pascal. IMPLEMENTATION OF IF THEN ELSE ============================== The if statements of the design are written, in Pascal, similarly except you do not need to include the ifend statement. 26 if condition then begin statement 1; statement 2; ........ etc end else begin statement 1; statement 2; ......... etc end; Note that the Pascal reserved words BEGIN and END mark the start and finish of multiple statements. If there is only one statement to be executed then the BEGIN and END may be excluded. Lets discuss the implementation of our design concerning the married or single survey. I have added to the basic design by stipulating an interactive program that prompts the user to respond 'y' or 'n' to a question. This introduces us to the data structure CHAR. The data type char, provided by Pascal, enables us to manipulate character sets including upper-case letters, the digits and some punctuation marks and mathematical symbols. When the user inputs a character - in this case either 'y' or 'n', the character input is assigned to the char variable RESPONSE. By using the if... then... else... control structure the program determines what the user input; the users response.If it is 'y' then the a message is output, if it is 'n' then a different message is output. Please note that when testing for characters they should be included in single quotation marks. When inputting characters they should be written as they are - without quotation marks. program ismarried; var response : char; begin write('Are you married ( enter y or n ) > '); read(response); if response = 'y' {Condition test. Note test for character uses ''} then writeln('You lucky person') else writeln('What''s your phone number?'); {Ifend} writeln; 27 end. A condition is a test which is carried out on the values of variables to compare them in some way. As your knowledge of Pascal evolves their use will become more apparent to you. For now lets summarize the various tests for conditions in a table. Operator Condition ======== ========= > is greater than < is less than = is equal to <> is not equal to >= is greater than or equal to <= is less than or equal to You can probably see, that comparison operators are a powerful programming tool that can be used to test for a variety of conditions. Now try the following exercises. Solutions will be given at the end of the article. Ex1 ____________________________________________________________ Devise a design to read in two numbers, compare them and print out the two numbers in ascending order. Ex2 Length, width and height are integer variables and woman, man and child are char variables. Which of the following are valid conditions? Solutions are given at end of article. (a) length > 24 (b) woman = 'Wendy' (c) child = 5 (d) height = 63 (e) man >= 70 (f) width = '6' (g) child = 'C' 28 IMPLEMENTATION OF THE WHILE DO LOOP =================================== While loops are implemented in Pascal as given below while condition do begin statement1; statement2; .........etc end; The statements to be executed repeatedly are separated by semicolons and if there is only one statement within the loop the keywords BEGIN and END may be exclude. Now lets go back to our design from the REPETITION section in the article and implement this in Pascal. program addup; { Adds up and prints total, to the screen, of real numbers input by the user } var total,number : real; begin total := 0; {Initialize variables updated in loop} write('Enter a positive number (negative number to exit) > '); readln(number); while number >= 0 do { start of loop } begin total := total + number; writeln; write('Enter a positive # (neg. # to exit) > '); readln(number) end;{ of while do loop } writeln; writeln('The total sum of numbers entered is ',total:5:2) end. As you can see the actual implementation of the design into Pascal was fairly straightforward, the brain work was required at the design stage. So while you are in the mood try out the following exercise :- Ex3 Design a program that will read in the ages of all the participants, the program will keep a count of the total amount of users and will print out the maximum age plus the total amount of users. Hint - do not forget the sentinel. 29 Before I give the solutions to the exercises I thought I would include this useful Turbo Pascal routine that checks the amount of space available on a default hard disk. The routine is not really aimed at beginners but gives you an insight into the power of Turbo Pascal and it's control over DOS. To more experienced users, feel free to modify the code to suit your own needs. program HDcheck; uses crt,dos; var i : integer; totalspace,usedspace,freespace : real; begin clrscr; writeln; totalspace := disksize(0)/1000000; writeln('Total disk space = ',totalspace:5:2,' MBytes'); writeln; usedspace := (disksize(0) - diskfree(0))/1000000; writeln('Disk space used = ',usedspace:5:2,' MBytes'); writeln; freespace := diskfree(0)/1000000; writeln('Space free = ',freespace:5:2,' MBytes'); for i := 1 to 15 do writeln; writeln('HDCheck by B.Gowans'); writeln; writeln('Press enter to continue'); readln; clrscr end. The use of control statements such as IF...THEN...ELSE and WHILE DO will greatly enhance your programming abilities and this is just the start of the control structures available to Pascal. Practice using these structures by following the examples found in the text books and get comfortable in using them. Here are the solutions to the exercises Ex1 : 1 initialize variables 2 compare the numbers 3 write out numbers in order 1.1 read in number1 30 1.2 read in number2 2.1 if number1 > number2 2.2 then 2.3 write out number1 2.4 write out number2 2.5 else 2.6 write out number2 2.7 write out number1 2.8 ifend Ex2 : (a) valid (b) invalid - variable is of type char and not string. (c) invalid - variable is of type char and not integer. (d) valid (e) invalid - for same reasons as (c). (f) invalid - width is integer variable and not char. (g) valid. Ex3 : 1. initialize variables 2. loop while users to be processed 3. process age 4. process count 5. loopend 6. write out count, maxage Further refinement :- 1.1 set count to 0 1.2 set max to 0 1.3.1 write out 'Please enter age ( enter -1 to quit )' 1.3.2 read in age 2.1 loop while age < > -1 3.1 if age > max 3.2 then 3.3 set max to age 3.4 else 3.5 { do nothing } 3.6 ifend 4.1 set count to count + 1 4.2 write out 'Please enter age ( enter -1 to quit )' 4.3 read in age 5 loopend 6.1 write out 'The maximum age was ',maxage 6.2 write out 'Out of a total of ',count,' people.' The implementation in Pascal follows - please note there may 31 be small changes in the implementation which does not affect the overall design. program age_survey; var age : integer; maxage : integer; count : integer; begin write('Enter age to nearest year ( -1 to quit ) > '); readln(age); count := 0; maxage := 0; while age <> -1 do { start loop, stop with age = -1 } begin if age > maxage { if start } then maxage := age else { do nothing };{no other branching is necessary so ifend} count := count + 1; writeln; write('Enter age to nearest year (-1 to quit ) > '); readln(age) end; writeln; writeln('The greatest age was ',maxage,' years'); writeln; writeln('The total number surveyed was ',count) end. All the code in this article has been written and compiled using Turbo Pascal v4. The executable code was run on an Opus PC 5 under DOS 3.3. There is no guarantee that the same code will run under your own particular configuration. 32 Recursion is Easy By Chris Burke Recursion and pointers seem to be the two subjects that cause beginners in pascal the most heartache. This brief article aims to delve into the subject of recursion. Introduction Imagine you (Alfred) are talking to Bob, when Christine walks up and rudely interrupts and starts talking to you. You put up with it and talk to Christine (she is prettier than Bob anyway). While talking to Christine, her mum (Daphne) wants a bit of a chat - you being the wise person you are (and liking Christine so much) decide to talk to Daphne, just after finishing your chat and commencing conversing with Christine, the telephone rings - you answer and (OH NO!!) it is Elaine wanting to go to the movies tonight, you quickly say sure no problems see you at 8, see you then, bye and continue talking to Christine. After a little while Christine leaves and you restart talking to Bob. So, What have you been doing - well 1. You have been two-timing, and deserve all you get. 2. You have been using your 'Talk' function recursively. i.e. Talk(Bob) Talk(Christine) Talk(Daphne) Return to Talk(Christine) Talk(Elaine) Return to Talk(Christine) Return to Talk(Bob) So now that you understand a bit about the concepts of recursion lets go onto more computer oriented problems of recursion. One of the most common examples of recursion is the factorial program - as it gets quoted so often I shall use the example here. For those not familiar with what a factorial is - here is an explanation : The factorial of a positive number (0,1,2...) is defined as follows : factorial of N = N*(N-1)*(N-2)....*1 factorial of 0 = 1 (by definition - i.e. for no real reason) 33 (For those with a little curiosity, there is a related function called the gamma function which works on negative numbers and non-integers.) The factorial is represented using the ! symbol i.e. 12! represents the factorial of 12 The first way the programmer would go about defining a function to calculate the factorial is as follows : function factorial(N:longint):longint; var Count,Temp:longint; begin Temp:=1; for Count:=1 to N do Temp:=Temp*Count; Factorial:=Temp; end; which would in effect for N=5 calculate the following factorial(N) = 1*2*3*4*5 which adheres to the formula above. The Quantum Leap The quantum leap in thought is to realize that the above formula: N!=N*(N-1)*(N-2)*(N-3)...*1 can be written differently. Lets take an example, say 5!. 5!=5*(5-1)*(5-2)*(5-3)*(5-4) or =5* 4 * 3 * 2 * 1 BUT LOOK!! this can be written =5* 4! and 4!=4*3*2*1 = 4*3! AND SO ON..... which means the original formula can be written as N!=N*(N-1)! - This is the mathematical equivalent of recursion. converting this to pascal is a fairly simple step : 34 function factorial(N:longint):longint; begin factorial:=N*factorial(N-1) end; EASY - just a straight implementation in pascal.... The only thing left to do is to implement what is known as an exit condition - or a termination condition, this tells the above factorial function when to stop what is currently an infinite loop, the easiest way to do this is to realize that factorial(0)=1 as defined by our mathematical gurus. The modified code is : function factorial(N:longint):longint; begin if N=0 then factorial:=1 else factorial:=N*factorial(N-1) end; This is where the IDE environment in the turbo pascal compiler comes in handy. Write a little program that includes the above function as follows : program TestRecursion; function factorial(N:longint):longint; begin if N=0 then factorial:=1 else factorial:=N*factorial(N-1) end; begin writeln('Factorial 5=',factorial(5)); writeln('Factorial 0=',factorial(0)); end. Try tracing through this code remembering to use the 'step over' on the 'begin' and the 'step into' on the two writeln statements. After a little playing and placing watches on N. (Don't forget to put Local Symbols and Debug Information on in the options). By now recursion should be somewhat more obvious. Something A Little More Useful This subject may be of a little more interest, but as I have always found - the best way of learning is by doing, and when you have it working you will be far more satisfied. The following hints should get you on your way to a routine which does some function MYFUNC on every file on your hard disk - in every directory on your hard drive. 35 In english here is what you would do : 1. Write a routine which does MYFUNC on every file in a specified directory something like the following : procedure DoMyFuncHere(D:Pathstring); var F:FileString; begin F:=GetFirstName; (* Remember : *) while F is valid do (* we want all directories and files *) begin MYFUNC(D+F); F:=GetNextName; end; end; test this procedure out and see if it works - OK 2. Modify this routine so that it calls itself recursively when there is a subdirectory (remember - don't get worried by the fact that the routine is calling itself) { Ed. Note: Semi-Pascal algorithm } | procedure DoMyFuncHere(D:Pathstring); | var | F:FileString; | | begin | F:=GetFirstName; (* Remember : *) | while F is valid do (* we want all directories and files *) | begin | if (F='.') or (F='..') then | DontGoHereThereIsProblemsIfYouDo; | else if F_IsADirectory then | DoMyFuncHere(D+F) | else MYFUNC(D+F); | F:=GetNextName; | end; | end; { Ed. Note: Actual Pascal algorithm } ~program test; ~uses crt,dos; ~ ~ Procedure MyFunc (FileRec : SearchRec); ~ begin ~ Writeln(FileRec.Name); ~ end; ~ 36 ~ procedure DoMyFuncHere(D:Pathstr); ~ var ~ SR : SearchRec; ~ ~ begin ~ FindFirst(D+'*.*',Directory+Archive,SR); ~ while DosError = 0 do ~ begin ~ if (SR.Name <> '.') AND (SR.Name <> '..') then ~ If (SR.Attr AND Directory) > 0 then ~ DoMyFuncHere(SR) ~ else ~ MYFUNC(D+SR.NAME); ~ FindNext(SR); ~ end; ~ end; ~ ~begin ~ DoMyFuncHere('C:\'); ~end. Try this out by calling DoMyFuncHere('C:\'); I am sure you can put your imagination to other uses for such a recursive directory engine, ie: finding and deleting ALL BAK files on a drive, or counting the size of subdirectories, you can modify the search criterion in the FINDFIRST line. A BIT OF EXPLAINING ------------------ Imagine a simple directory like this, COMMAND.COM 1 calls MYFUNC AUTOEXEC.BAT 1 calls MYFUNC 1 calls DoMyFuncHere - recursively <.> 2 skips <..> 2 skips FDISK.COM 2 calls MYFUNC DEBUG.COM 2 calls MYFUNC 2 returns from second level CONFIG.SYS 1 calls MYFUNC 1 calls DoMyFuncHere - recursively <.> 2 skips <..> 2 skips LIST.COM 2 calls MYFUNC 2 calls DoMyFuncHere - recursively <.> 3 skips <..> 3 skips SI.EXE 3 calls MYFUNC 3 returns from third level 37 4EDIT.EXE 2 calls MYFUNC 2 returns from second level 4DOS.COM 1 calls MYFUNC 1 returns and exits to main program By following the above (the number represents how deep you have gone) you should be able to see how simple the program to look at every file on your hard disk is. If you want any more assistance on recursion see the section above called 'A BIT OF EXPLAINING' otherwise continue to the conclusion. Conclusion I hope that this description of recursion will answer your questions on recursion, if you have any questions for me then I have a point called 'Mindware' fidonet 3:640/305.1 Christopher Burke Brisbane, Queensland AUSTRALIA Chris Burke is owner/operator of a contract Programming company called Mindware in Brisbane, Australia. He programs commercially using various languages including Turbo Pascal from version 2.0 through to version 5.5 professional (Using Object Professional), Assembler, and Clarion. He runs a point Fidonet system, and can be contacted at 3:640/305.1. 38 Conclusion There are a couple things I'd like to point about some future issues. Bob Gowans will, of course, continue to produce his column, which has become incredibly popular. Richard is going to give us quite bit of stuff on graphics in Pascal, so that will also be a regular contribution. I will be starting a series pretty soon on doing large projects. I am just finishing up a large project that involved 5 other programmers and quite a bit of design work before the implementation. I'd like to go over a lot of the stuff we did. This alone should take several issues, but I think you will find it very interesting. The code alone, because it is so vast, will be broken up into several issues and each unit will be covered alone and then the final integration of the entire project will be examined. I feel bad that I can't seem to get the newsletter out as often as I would like, but like many of you, I have to set my priorities, one of them, earning a living. Hopefully you will forgive me for this. All I can do is make a promise that I will try my best to get each issue out as soon as possible. I would also like to ask, as I do in every issue, that you try to find the time to send in some of your own material for inclusion in the newsletter. Without user contributions, I just can't keep this going. They have been good lately, but I could handle a few more. So, please, if you don't feel you can contribute, try to find friends that can. Try to get them to send in stuff they've done. One last word on the distribution list. I keep quite a bit of the updates in a directory which I search at the end of the month to make updates to the distribution list. Several days ago, because of a strange problem, that directory, almost exclusively, was completely wiped out. I have been unable to make any corrections to the distribution list. If you sent in a correction, or addition, please send me the information again so that I can make those corrections. I'm sorry for any inconvenience this may have caused. Ah, and one final thing that I almost forgot. I have recently put a second computer together. This should help me put a little more time into the newsletter, as I won't have to worry about bringing my BBS down every time I want to work on the newsletter. Well, enjoy the holidays and hopefully I'll have a brand new issue out in early January. _Pete Davis 39 The Pascal NewsLetter is Copyrighted by Pete Davis. Articles submitted by others are the property of the authors and are used with permission. They may not be treated separately from this newsletter without the author's permission and thus maintain all distribution rules of the newsletter as a whole. It may be freely distributed in un-modified form, but no charge whatsoever may be incurred on the recipient. All code is provided 'as-is' with no guarantees whatsoever. The Pascal NewsLetter can be obtained from the following locations: GEnie : General Electric Network for Information Exchange. It is located in the IBMPC filelist. Simtel : Internet address: 26.2.0.74 or Simtel20.ARPA It is located in the directory. Programmer's Forum : This is the home of the PNL. Each issue can be found in the MAG directory from the main area. The number is on the title page. If you would like to receive back issues of PNL directly from me, send a diskette and $2.00 for shipping. Don't forget to include your address. Send your order to: Peter Davis 4851 Brandywine St. NW Washington, DC 20016 If you are a SysOp that will regularly carry PNL and would like to have your bulletin board listed as such, here, send me a message either by postal mail or at one of the electronic addresses given on the title page, with your bulletin board's name, phone number, and your name. 40 Distribution List The following is the phone numbers to bulletin boards known to carry PNL. If you would like your bulletin board's name and number added to or deleted from this list, please send me a message at one of my many addresses. I can not guarantee whether a listed board will have any particular issue. The Programmer's Forum ................. Phone: (202) 966-3647 The Programmer's Forum is the home of the PNL. BBS | FidoNet Addrs | Phone # --------------------------+----------------------+--------------- The Bored | 1:397/3 | (512) 303-0471 Classic City BBS | 1:370/10 | (404) 548-0726 Thieve's World BBS | 1:106/805 | (713) 463-8053 Hippocampus BBS | 1:141/205 | (203) 484-4621 Rogers State College | 1:170/708 | (918) 341-8982 The Foxtrot BBS | 1:272/26 | (914) 567-1814 Turbo City BBS | 1:208/2 | (209) 599-7435 Austin IEMUG/MIDI BBS | 1:382/14 | (512) 258-0626 Laser Publishers | 1:170/403 | (918) 438-2749 Fargo RBBS-PC | 1:10/8 | (701) 293-5973 Momentary Lapse of Reason | | (704) 327-6361 The Demon's Den | | (508) 433-2702 The Cannibal Cafe | | (508) 840-6589 IBM Tech FIDO | | (508) 433-6491 The User Friendly BBS | | (704) 323-8223 The Disseminator BBS | 3:640/378 | 61-7-368-1239 Beyound Fronttiers BBS | 2:230/101 | (+45)8694-1609 Collision Theory | | (703) 503-9441 Merlin's Mailbox | 2:245/39 | Ed-Net9600 | 1:153/734 | (604) 732-8877